package io. hei.commons.dynamic.datasource.aspect;

import io. hei.commons.dynamic.datasource.annotation.DataSource;
import io. hei.commons.dynamic.datasource.config.DynamicContextHolder;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * 多数据源，切面处理类
 *
 *
 * @since 1.0.0
 */
// 声明、定义切面类
@Aspect
@Component
/**
 * 让该bean的执行顺序优先级最高，并不能控制加载入IoC的顺序
 * 如果一个方法被多个 @Around 增强，那就可以使用该注解指定顺序
 */
@Order(Ordered.HIGHEST_PRECEDENCE)
public class DataSourceAspect {
    protected Logger logger = LoggerFactory.getLogger(getClass());

    // 指明通知在使用@DataSource注解标注下才触发
    @Pointcut("@annotation(io. hei.commons.dynamic.datasource.annotation.DataSource) " +
            "|| @within(io. hei.commons.dynamic.datasource.annotation.DataSource)")
    public void dataSourcePointCut() {

    }

    // 对通知方法的具体实现并采用环绕通知设定方法与切面的执行顺序，即在方法执行前和后触发
    @Around("dataSourcePointCut()")
    /**
     * ProceedingJoinPoint继承了JoinPoint，相较于JoinPoint暴露了proceed方法，该类仅配合实现around通知
     * JoinPoint类，用来获取代理类和被代理类的信息
     * 调用proceed方法，表示继续执行目标方法（即加了@DataSource注解的方法）
     */
    public Object around(ProceedingJoinPoint point) throws Throwable {

        // 通过反射获得被代理类（目标对象）
        Class targetClass = point.getTarget().getClass();
        System.out.println("targetClass:" + targetClass);
        /**
         * 获得被代理类（目标对象）的方法签名
         * signature加签是一种简单、 低成本、保障数据安全的方式
         */
        MethodSignature signature = (MethodSignature) point.getSignature();
        /**
         * 获得被代理类（目标对象）的方法
         * 这里获得方法也可以通过反射和getTarget()，但步骤更多更复杂
         */
        Method method = signature.getMethod();
        System.out.println("method:" + method);

        // 获得被代理类（目标对象）的注解对象
        DataSource targetDataSource = (DataSource) targetClass.getAnnotation(DataSource.class);
        System.out.println("targetDataSource:" + targetDataSource);
        // 获得被代理类（目标对象）的方法的注解对象
        DataSource methodDataSource = method.getAnnotation(DataSource.class);
        System.out.println("methodDataSource:" + methodDataSource);
        // 判断被代理类（目标对象）的注解对象或者被代理类（目标对象）的方法的注解对象不为空
        if (targetDataSource != null || methodDataSource != null) {
            String value;
            // 优先用被代理类（目标对象）的方法的注解对象的值进行后续赋值
            if (methodDataSource != null) {
                value = methodDataSource.value();
            } else {
                value = targetDataSource.value();
            }

            /**
             * DynamicContextHolder是自己实现的栈数据结构
             * 将注解对象的值入栈
             */
            DynamicContextHolder.push(value);
            logger.debug("set datasource is {}", value);
        }

        try {
            // 继续执行被代理类（目标对象）的方法
            return point.proceed();
        } finally {
            // 清空栈中数据
            DynamicContextHolder.poll();
            logger.debug("clean datasource");
        }
    }
}